package vsy.buzzwordbingo.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.NotSupportedException;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.thoughtworks.xstream.XStream;

import vsy.buzzwordbingo.model.DuplicatePlayerException;
import vsy.buzzwordbingo.model.Game;
import vsy.buzzwordbingo.model.GameBoard;
import vsy.buzzwordbingo.model.GameBoardUpdateException;
import vsy.buzzwordbingo.model.GameCore;
import vsy.buzzwordbingo.model.Player;
import vsy.buzzwordbingo.model.impl.BoardBuilder;
import vsy.buzzwordbingo.model.impl.BuzzwordBuilder;
import vsy.buzzwordbingo.model.impl.GameBuilder;
import vsy.buzzwordbingo.model.impl.LinkBuilder;
import vsy.buzzwordbingo.model.impl.PlayerBuilder;
import vsy.buzzwordbingo.model.impl.PlayerImpl;
import vsy.buzzwordbingo.sort.ComparatorProvider;
import vsy.buzzwordbingo.util.ControllerUtil;

@Controller
public class RestController {

	@Resource
	private GameCore gameCore;

	@Resource
	private ComparatorProvider comparatorProvider;

	@RequestMapping(value = "/rest", method = RequestMethod.GET)
	public ModelAndView home(HttpServletRequest request,
			HttpServletResponse response) {
		prepare(request);
		return new ModelAndView("restResources");
	}

	@RequestMapping(value = "/rest/games", method = RequestMethod.GET)
	public ModelAndView getGames(HttpServletRequest request,
			HttpServletResponse response,
			@RequestParam(required = false) String from,
			@RequestParam(required = false) String to,
			@RequestParam(required = false) String sort,
			@RequestParam(required = false) String order,
			@RequestParam(required = false) String player) {

		List<Game> allGames = gameCore.getGames();

		Comparator<Game> comparator = comparatorProvider.getComparator(
				Game.class, sort, order);
		Collections.sort(allGames, comparator);

		if (player != null) {
			Player filteredPlayer = gameCore.getPlayer(player);
			Iterator<Game> playerIterator = allGames.iterator();
			while (playerIterator.hasNext()) {
				if (!playerIterator.next().getPlayers()
						.contains(filteredPlayer))
					playerIterator.remove();
			}
		}

		PaginatedList<Game> games = new PaginatedList<Game>(allGames, from, to,
				100);

		preparePagination(request, "games", games);
		prepare(request);
		return new ModelAndView("restGames");
	}

	@RequestMapping(value = "/rest/games/{id}", method = RequestMethod.GET)
	public ModelAndView getGame(HttpServletRequest request,
			HttpServletResponse response, @PathVariable String id) {

		Game game = gameCore.getGame(id);

		if (request.getUserPrincipal() != null
				&& request.getUserPrincipal().getName() != null) {
			gameCore.poll(id, request.getUserPrincipal().getName());
		}

		if (game == null) {
			return ControllerUtil.createEmptyResponse(
					"application/buzzwordbingo.game+xml", HttpStatus.NOT_FOUND);
		}

		request.setAttribute("game", game);
		prepare(request);
		return new ModelAndView("restGame");
	}

	@RequestMapping(value = "/rest/gamesFUCK/{id}", method = RequestMethod.GET)
	public ModelAndView getGameFUCK(HttpServletRequest request,
			HttpServletResponse response, @PathVariable String id) {

		Game game = gameCore.getGame(id);

		if (request.getUserPrincipal() != null
				&& request.getUserPrincipal().getName() != null) {
			gameCore.poll(id, request.getUserPrincipal().getName());
		}

		if (game == null) {
			return ControllerUtil.createEmptyResponse(
					"application/buzzwordbingo.game+xml", HttpStatus.NOT_FOUND);
		}

		request.setAttribute("game", game);
		prepare(request);
		return new ModelAndView("restGame");
	}

	
	@RequestMapping(value = "/rest/players", method = RequestMethod.GET)
	public ModelAndView getPlayers(HttpServletRequest request,
			HttpServletResponse response,
			@RequestParam(required = false) String from,
			@RequestParam(required = false) String to,
			@RequestParam(required = false) String sort,
			@RequestParam(required = false) String order,
			@RequestParam(required = false) String game) {

		List<Player> allPlayers = gameCore.getPlayers();

		if (game != null) {
			Game filteredGame = gameCore.getGame(game);
			Iterator<Player> playerIterator = allPlayers.iterator();
			while (playerIterator.hasNext()) {
				if (!playerIterator.next().getGames().contains(filteredGame))
					playerIterator.remove();
			}
		}

		Comparator<Player> comparator = comparatorProvider.getComparator(
				Player.class, sort, order);
		Collections.sort(allPlayers, comparator);

		PaginatedList<Player> players = new PaginatedList<Player>(allPlayers,
				from, to, 100);

		preparePagination(request, "players", players);
		prepare(request);
		return new ModelAndView("restPlayers");
	}

	@RequestMapping(value = "/rest/players/{name}", method = RequestMethod.GET)
	public ModelAndView getPlayer(HttpServletRequest request,
			HttpServletResponse response, @PathVariable String name) {

		Player player = gameCore.getPlayer(name);

		if (player == null) {
			return ControllerUtil.createEmptyResponse(
					"application/buzzwordbingo.player+xml",
					HttpStatus.NOT_FOUND);
		}

		request.setAttribute("player", player);
		prepare(request);
		return new ModelAndView("restPlayer");
	}

	@RequestMapping(value = "/rest/boards", method = RequestMethod.GET)
	public ModelAndView getBoards(HttpServletRequest request,
			HttpServletResponse response,
			@RequestParam(required = false) String from,
			@RequestParam(required = false) String to,
			@RequestParam(required = false) String game,
			@RequestParam(required = false) String player)
			throws NotSupportedException {

		List<GameBoard> allBoards;
		if (game != null && player != null) {
			allBoards = new ArrayList<GameBoard>(1);
			allBoards.add(gameCore.getBoard(game, player));
		} else if (player != null) {
			allBoards = gameCore.getPlayer(player).getBoards();
		} else if (game != null) {
			allBoards = gameCore.getGame(game).getBoards();
		} else {
			allBoards = gameCore.getBoards();
		}

		if (game != null || player != null) {
			Game filteredGame = (game == null) ? null : gameCore.getGame(game);
			Player filteredPlayer = (player == null) ? null : gameCore
					.getPlayer(player);
			Iterator<GameBoard> boardIterator = allBoards.iterator();
			while (boardIterator.hasNext()) {
				GameBoard board = boardIterator.next();
				boolean removed = false;
				if (filteredGame != null
						&& !board.getGame().equals(filteredGame)) {
					boardIterator.remove();
					removed = true;
				}
				if (!removed && filteredPlayer != null
						&& !board.getPlayer().equals(filteredPlayer))
					boardIterator.remove();
			}
		}

		PaginatedList<GameBoard> boards = new PaginatedList<GameBoard>(
				allBoards, from, to, 100);

		preparePagination(request, "boards", boards);
		prepare(request);
		return new ModelAndView("restBoards");
	}

	@RequestMapping(value = "/rest/boards/{id}", method = RequestMethod.GET)
	public ModelAndView getBoard(HttpServletRequest request,
			HttpServletResponse response, @PathVariable String id) {

		GameBoard board = gameCore.getBoard(id);
		request.setAttribute("board", board);

		prepare(request);
		return new ModelAndView("restBoard");
	}

	@RequestMapping(value = "/rest/players", method = RequestMethod.POST)
	public ModelAndView loginPlayer(HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		XStream xstream = new XStream();
		xstream.alias("player", PlayerBuilder.class);
		Player player = ((PlayerBuilder) xstream.fromXML(request
				.getInputStream())).createPlayer();

		try {
			gameCore.loginPlayer(player);
		} catch (DuplicatePlayerException e) {
			response.setContentType("text/xml");
			return ControllerUtil.createEmptyResponse("text/xml",
					HttpStatus.CONFLICT);
		}

		response.setHeader("Location", getServerAddress(request)
				+ "/rest/players/" + player.getName());
		response.setContentType("text/xml");
		return ControllerUtil.createEmptyResponse("text/xml",
				HttpStatus.CREATED);
	}

	@RequestMapping(value = "/rest/players/{name}", method = RequestMethod.DELETE)
	public ModelAndView logoutPlayer(HttpServletRequest request,
			HttpServletResponse response, @PathVariable String name)
			throws IOException {

		if (gameCore.getPlayer(name) == null) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.NOT_FOUND);
		}

		String requestingPlayer = request.getUserPrincipal().getName();
		if (!requestingPlayer.equals(name)) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.FORBIDDEN);
		}

		gameCore.logoutPlayer(name);

		return ControllerUtil.createEmptyResponse("text/plain",
				HttpStatus.NO_CONTENT);
	}

	@RequestMapping(value = "/rest/games", method = RequestMethod.POST)
	public ModelAndView createGame(HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		XStream xstream = new XStream();
		xstream.alias("game", GameBuilder.class);
		xstream.alias("buzzwords", ArrayList.class);
		xstream.alias("buzzword", String.class);
		Game game = ((GameBuilder) xstream.fromXML(request.getInputStream()))
				.createGame();

		String owner = request.getUserPrincipal().getName();
		if (game.getOwner() != null && !game.getOwner().equals(owner)) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.FORBIDDEN);
		}

		Game createdGame = gameCore.createGame(owner, game.getTitle(), game
				.getSize(), game.getDescription(), game.getBuzzwords());

		response.setHeader("Location", getServerAddress(request)
				+ "/rest/games/" + createdGame.getId());
		return ControllerUtil.createEmptyResponse("text/plain",
				HttpStatus.CREATED);
	}

	@RequestMapping(value = "/rest/games/{gameId}/players", method = RequestMethod.POST)
	public ModelAndView joinGame(HttpServletRequest request,
			HttpServletResponse response, @PathVariable String gameId)
			throws IOException {
		Game game = gameCore.getGame(gameId);
		if (game == null) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.NOT_FOUND);
		}

		XStream xstream = new XStream();
		xstream.alias("player", PlayerBuilder.class);
		Player player = ((PlayerBuilder) xstream.fromXML(request
				.getInputStream())).createPlayer();

		Player requestingPlayer = new PlayerImpl(request.getUserPrincipal()
				.getName(), null);
		if (!requestingPlayer.equals(player)) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.FORBIDDEN);
		}

		if (game.getPlayers().contains(requestingPlayer)) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.CONFLICT);
		}

		gameCore.joinGame(gameId, requestingPlayer.getName());

		// TODO Location
		return ControllerUtil.createEmptyResponse("text/plain",
				HttpStatus.CREATED);
	}

	@RequestMapping(value = "/rest/games/{gameId}/players/{name}", method = RequestMethod.DELETE)
	public ModelAndView leaveGame(HttpServletRequest request,
			HttpServletResponse response, @PathVariable String gameId,
			@PathVariable String name) throws IOException {
		Game game = gameCore.getGame(gameId);
		if (game == null) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.NOT_FOUND);
		}

		Player player = new PlayerImpl(name, null);
		if (!game.getPlayers().contains(player)) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.NOT_FOUND);
		}

		String requestingPlayer = request.getUserPrincipal().getName();
		if (!requestingPlayer.equals(player.getName())) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.FORBIDDEN);
		}

		gameCore.leaveGame(gameId, requestingPlayer);

		// TODO Location
		return ControllerUtil.createEmptyResponse("text/plain",
				HttpStatus.NO_CONTENT);
	}

	@RequestMapping(value = "/rest/boards/{id}", method = RequestMethod.PUT)
	public ModelAndView changeBoard(HttpServletRequest request,
			HttpServletResponse response, @PathVariable String id)
			throws IOException {
		GameBoard board = gameCore.getBoard(id);
		if (board == null) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.NOT_FOUND);
		}
		if (board.isFinished()) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.CONFLICT);
		}

		XStream xstream = new XStream();
		xstream.alias("board", BoardBuilder.class);
		xstream.alias("link", LinkBuilder.class);
		xstream.alias("buzzword", BuzzwordBuilder.class);
		GameBoard updatedBoard = ((BoardBuilder) xstream.fromXML(request
				.getInputStream())).createBoard();

		try {
			board.updateBuzzwords(updatedBoard);

			if (board.isFinished()) {
				gameCore.destroyGame(board.getGame().getId());
			}

		} catch (GameBoardUpdateException e) {
			return ControllerUtil.createEmptyResponse("text/plain",
					HttpStatus.BAD_REQUEST);
		}

		return ControllerUtil.createEmptyResponse("text/plain",
				HttpStatus.NO_CONTENT);
	}

	private class PaginatedList<T> extends ArrayList<T> {
		private static final long serialVersionUID = -2193285582777892978L;

		private int from;
		private int to;

		private int originalSize;

		public PaginatedList(List<T> list, String from, String to,
				int defaultCount) {
			originalSize = list.size();
			if (from == null && to == null) {
				this.from = 0;
				this.to = defaultCount - 1;
			} else if (to == null) {
				this.from = Integer.parseInt(from);
				this.to = this.from + defaultCount - 1;
			} else if (from == null) {
				this.to = Integer.parseInt(to);
				this.from = this.to - defaultCount + 1;
			} else {
				this.from = Integer.parseInt(from);
				this.to = Integer.parseInt(to);
			}
			if (this.from < 0)
				this.from = 0;
			if (this.to < 0)
				this.to = this.from + defaultCount - 1;
			for (int i = this.from; i < list.size() && i <= this.to; i++) {
				this.add(list.get(i));
			}
		}

		public int getPreviousFrom() {
			if (from <= 0)
				return -1;
			int count = to - from + 1;
			int previousFrom = from - count;
			if (previousFrom < 0)
				previousFrom = 0;
			return new Integer(previousFrom);
		}

		public int getPreviousTo() {
			if (from <= 0)
				return -1;
			int previousTo = from - 1;
			return new Integer(previousTo);
		}

		public int getNextTo() {
			if (to >= originalSize - 1)
				return -1;
			int count = to - from + 1;
			int nextTo = to + count;
			if (nextTo >= originalSize)
				nextTo = originalSize - 1;
			return new Integer(nextTo);
		}

		public int getNextFrom() {
			if (to >= originalSize - 1)
				return -1;
			int nextFrom = to + 1;
			return new Integer(nextFrom);
		}

		public int getFrom() {
			return from;
		}

		public int getTo() {
			return to;
		}
	}

	private static void preparePagination(HttpServletRequest request,
			String listName, PaginatedList<?> list) {
		request.setAttribute("from", list.getFrom());
		request.setAttribute("to", list.getTo());
		request.setAttribute("prevFrom", list.getPreviousFrom());
		request.setAttribute("prevTo", list.getPreviousTo());
		request.setAttribute("nextFrom", list.getNextFrom());
		request.setAttribute("nextTo", list.getNextTo());
		request.setAttribute(listName, list);
	}

	private static void prepare(HttpServletRequest request) {
		request.setAttribute("serverAddress", getServerAddress(request));
	}

	private static String getServerAddress(HttpServletRequest request) {
		String serverAddress;
		if (request.getServerPort() == 80)
			serverAddress = request.getScheme() + "://"
					+ request.getServerName();
		else
			serverAddress = request.getScheme() + "://"
					+ request.getServerName() + ":" + request.getServerPort();
		return serverAddress;
	}
}
